package com.pai4j.aigc.context.service;

import com.pai4j.aigc.context.entity.ChatMessageContextEntity;
import com.pai4j.aigc.context.entity.ChatMemorySummaryEntity;
import com.pai4j.aigc.context.entity.ChatSessionEntity;
import com.pai4j.aigc.context.model.ChatMessageContext;
import com.pai4j.aigc.context.repository.dao.IChatMemorySummaryDAO;
import com.pai4j.aigc.context.repository.dao.IChatMessageContextDAO;
import com.pai4j.aigc.context.repository.dao.IChatSessionDAO;
import com.pai4j.aigc.llm.LlmSummarizerService;
import com.pai4j.common.enums.RedisKeyEnum;
import com.pai4j.common.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ChatContextService {

    // hot cache ttl
    @Value("${aigc.context.ttlHours:24}")
    private long contextTtlHours;
    // redis list max length
    @Value("${aigc.context.maxRecent:50}")
    private int maxRecent;

    private static final int DEFAULT_CONTEXT_LIMIT = 20;
    // number of messages to trigger summarize
    @Value("${aigc.context.summary.threshold:200}")
    private int summaryThreshold;
    // keep recent messages in DB
    @Value("${aigc.context.summary.retainRecent:40}")
    private int retainRecent;
    // max chars to store in summary
    @Value("${aigc.context.summary.maxChars:4000}")
    private int summaryMaxChars;

    @Autowired
    private com.pai4j.common.util.RedisUtil redisUtil;
    @Autowired
    private IChatSessionDAO chatSessionDAO;
    @Autowired
    private IChatMessageContextDAO chatMessageContextDAO;
    @Autowired
    private IChatMemorySummaryDAO chatMemorySummaryDAO;

    @Autowired
    private LlmSummarizerService llmSummarizerService;
    /**
     * 确保指定的会话存在，如果不存在则创建一个新的会话。
     *
     * @param userId 用户ID
     * @param botId 机器人ID
     * @param preferSessionId 偏好的会话ID，如果为空或null，则使用默认生成的会话ID
     * @return 会话ID
     */
    public String ensureSession(String userId, String botId, String preferSessionId) {
        String sid = preferSessionId;
        if (sid == null || sid.isEmpty()) {
            // 优先复用该用户+bot的最近一次活动中的会话（状态为active=1）
            Optional<ChatSessionEntity> latest = chatSessionDAO
                    .findFirstByUserIdAndBotIdAndStatusOrderByUpdateDateDesc(userId, botId, 1);
            if (latest.isPresent()) {
                return latest.get().getSessionId();
            }
            // 否则创建一个新的随机会话ID
            sid = randomSessionId(userId, botId);
        }
        Optional<ChatSessionEntity> opt = chatSessionDAO.findBySessionId(sid);
        if (opt.isPresent()) {
            return sid;
        }
        ChatSessionEntity entity = new ChatSessionEntity();
        entity.setSessionId(sid);
        entity.setUserId(userId);
        entity.setBotId(botId);
        entity.setStatus(1);
        entity.setTitle(null);
        entity.setLastMessageAt(new Date());
        chatSessionDAO.save(entity);
        return sid;
    }

    /**
     * 以JSON数组字符串形式返回最近的上下文，优先从JSON缓存读取，不足时回源并回写。
     */
    public String loadRecentJson(String sessionId, int limit) {
        String jsonKey = RedisKeyEnum.CONTEXT_JSON_SESSIONID.getKey(sessionId);
        String arr = redisUtil.get(jsonKey);
        if (arr != null && !arr.isEmpty()) {
            try {
                List<ChatMessageContext> list = JsonUtil.fromJson(arr, new com.fasterxml.jackson.core.type.TypeReference<List<ChatMessageContext>>(){});
                if (list != null) {
                    if (list.size() > limit) {
                        list = list.subList(list.size() - limit, list.size());
                    }
                    return JsonUtil.toJsonString(list);
                }
            } catch (Exception ignore) {}
        }
        // fallback to existing loadRecent then cache
        List<ChatMessageContext> recent = loadRecent(sessionId, limit);
        String json = JsonUtil.toJsonString(recent);
        redisUtil.set(jsonKey, json);
        redisUtil.expire(jsonKey, contextTtlHours * 3600);
        return json;
    }

    /**
     * 生成默认的会话ID，基于用户ID和机器人ID生成一个确定性的哈希值。
     *
     * @param userId 用户ID
     * @param botId 机器人ID
     * @return 默认的会话ID
     */
    public String defaultSessionId(String userId, String botId) {
        String raw = String.format("u:%s|b:%s|d", userId, botId);
        return shortHash(raw);
    }

    /**
     * 生成随机会话ID，用于在未显式指定sessionId时创建新会话，支持同一用户同一bot进行多轮独立会话。
     * 使用不带连字符的UUID，长度32，满足列长度约束（<=64）。
     */
    private String randomSessionId(String userId, String botId) {
        return UUID.randomUUID().toString().replace("-", "");
    }

    /**
     * 显式创建一个新的会话（不复用历史）。
     */
    @Transactional
    public String createNewSession(String userId, String botId) {
        String sid = randomSessionId(userId, botId);
        ChatSessionEntity entity = new ChatSessionEntity();
        entity.setSessionId(sid);
        entity.setUserId(userId);
        entity.setBotId(botId);
        entity.setStatus(1);
        entity.setTitle(null);
        entity.setLastMessageAt(new Date());
        chatSessionDAO.save(entity);
        return sid;
    }

    /**
     * 对输入字符串生成一个短哈希值（前8字节的MD5摘要）。
     *
     * @param raw 输入字符串
     * @return 哈希值的十六进制表示
     */
    private String shortHash(String raw) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(raw.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 8; i++) { // 16 hex chars = 64 bits
                sb.append(String.format("%02x", digest[i]));
            }
            return sb.toString();
        } catch (Exception e) {
            return Integer.toHexString(raw.hashCode());
        }
    }

    /**
     * 检测是否为“角色设定”模板化 Prompt 内容（不需要存入上下文/数据库）。
     * 该方法为兜底防御：即使上游误传入模板 Prompt，也不入库。
     */
    private boolean isRoleSettingPrompt(String content) {
        if (content == null) return false;
        String c = content;
        // 常见模板特征：包含“# 角色设定”、“# 我的问题”/“我给的内容”以及代码块 ```
        boolean hasRoleHeader = c.contains("# 角色设定") || c.contains("角色设定");
        boolean hasQuestionHeader = c.contains("# 我的问题") || c.contains("我给的内容");
        boolean hasCodeFence = c.contains("```") || c.contains("\r\n```");
        // 具体项目默认角色的片段（尽量宽松匹配，避免漏判）
        boolean containsDefaultRole = c.contains("pai4j智能对话助手机器人") || c.contains("AgentPAI");
        // 满足多个特征时判定为模板 Prompt
        int score = 0;
        if (hasRoleHeader) score++;
        if (hasQuestionHeader) score++;
        if (hasCodeFence) score++;
        if (containsDefaultRole) score++;
        return score >= 2; // 至少命中两项特征才过滤
    }

    /**
     * 加载指定会话的最近消息，优先从Redis缓存中读取，不足时从数据库中补充。
     *
     * @param sessionId 会话ID
     * @param limit 最多加载的消息数量
     * @return 最近的消息列表
     */
    public List<ChatMessageContext> loadRecent(String sessionId, int limit) {
        String jsonKey = RedisKeyEnum.CONTEXT_JSON_SESSIONID.getKey(sessionId);
        try {
            String arr = redisUtil.get(jsonKey);
            if (arr != null && !arr.isEmpty()) {
                List<ChatMessageContext> list = JsonUtil.fromJson(arr, new com.fasterxml.jackson.core.type.TypeReference<List<ChatMessageContext>>(){});
                if (list != null && !list.isEmpty()) {
                    if (list.size() > limit) {
                        list = list.subList(list.size() - limit, list.size());
                    }
                    return list;
                }
            }
        } catch (Exception ignore) {}
        // fallback to DB only
        List<ChatMessageContextEntity> dbList = chatMessageContextDAO.findBySessionIdOrderByIdDesc(sessionId, PageRequest.of(0, limit));
        List<ChatMessageContext> fromDb = dbList.stream()
                .sorted(Comparator.comparingLong(ChatMessageContextEntity::getId))
                .map(e -> {
                    ChatMessageContext c = new ChatMessageContext();
                    c.setRole(e.getRole());
                    c.setContent(e.getContent());
                    c.setName(e.getName());
                    c.setPartial(e.getPartial());
                    return c;
                })
                .collect(Collectors.toList());
        // write back to JSON cache for next time
        try {
            redisUtil.set(jsonKey, JsonUtil.toJsonString(fromDb));
            redisUtil.expire(jsonKey, contextTtlHours * 3600);
        } catch (Exception ignore) {}
        return fromDb;
    }

    /**
     * 向指定会话中追加一条消息，并同时写入Redis缓存和数据库。
     *
     * @param sessionId 会话ID
     * @param role 消息角色（如 user 或 assistant）
     * @param content 消息内容
     */
    @Transactional
    public void append(String sessionId, String role, String content) {
        // 兜底过滤：角色设定模板不入库/缓存
        if (isRoleSettingPrompt(content)) {
            log.debug("skip role-setting prompt persistence, sid:{}, role:{}", sessionId, role);
            return;
        }
        String jsonKey = RedisKeyEnum.CONTEXT_JSON_SESSIONID.getKey(sessionId);
        ChatMessageContext ctx = new ChatMessageContext();
        ctx.setRole(role);
        ctx.setContent(content);
        // update JSON-array cache only
        try {
            String arr = redisUtil.get(jsonKey);
            List<ChatMessageContext> list;
            if (arr == null || arr.isEmpty()) {
                list = new ArrayList<>();
            } else {
                list = JsonUtil.fromJson(arr, new com.fasterxml.jackson.core.type.TypeReference<List<ChatMessageContext>>(){});
                if (list == null) list = new ArrayList<>();
            }
            list.add(ctx);
            if (list.size() > maxRecent) {
                list = list.subList(list.size() - maxRecent, list.size());
            }
            redisUtil.set(jsonKey, JsonUtil.toJsonString(list));
            redisUtil.expire(jsonKey, contextTtlHours * 3600);
        } catch (Exception e) {
            log.warn("update json context cache error, sid:{}", sessionId, e);
        }
        // persist to DB
        ChatMessageContextEntity entity = new ChatMessageContextEntity();
        entity.setSessionId(sessionId);
        entity.setRole(role);
        entity.setContent(content);
        chatMessageContextDAO.save(entity);
        // update session last time
        chatSessionDAO.findBySessionId(sessionId).ifPresent(s -> {
            s.setLastMessageAt(new Date());
            chatSessionDAO.save(s);
        });
    }

    /**
     * 追加一条带有名称和partial标记的消息。
     */
    @Transactional
    public void append(String sessionId, String role, String content, String name, Boolean partial) {
        // 兜底过滤：角色设定模板不入库/缓存
        if (isRoleSettingPrompt(content)) {
            log.debug("skip role-setting prompt persistence, sid:{}, role:{}, name:{}", sessionId, role, name);
            return;
        }
        String jsonKey = RedisKeyEnum.CONTEXT_JSON_SESSIONID.getKey(sessionId);
        ChatMessageContext ctx = new ChatMessageContext();
        ctx.setRole(role);
        ctx.setContent(content);
        ctx.setName(name);
        ctx.setPartial(partial);
        // update JSON-array cache only
        try {
            String arr = redisUtil.get(jsonKey);
            List<ChatMessageContext> list;
            if (arr == null || arr.isEmpty()) {
                list = new ArrayList<>();
            } else {
                list = JsonUtil.fromJson(arr, new com.fasterxml.jackson.core.type.TypeReference<List<ChatMessageContext>>(){});
                if (list == null) list = new ArrayList<>();
            }
            list.add(ctx);
            if (list.size() > maxRecent) {
                list = list.subList(list.size() - maxRecent, list.size());
            }
            redisUtil.set(jsonKey, JsonUtil.toJsonString(list));
            redisUtil.expire(jsonKey, contextTtlHours * 3600);
        } catch (Exception e) {
            log.warn("update json context cache error, sid:{}", sessionId, e);
        }
        ChatMessageContextEntity entity = new ChatMessageContextEntity();
        entity.setSessionId(sessionId);
        entity.setRole(role);
        entity.setContent(content);
        chatMessageContextDAO.save(entity);
        chatSessionDAO.findBySessionId(sessionId).ifPresent(s -> {
            s.setLastMessageAt(new Date());
            chatSessionDAO.save(s);
        });
    }

    /**
     * 直接传入上下文对象追加。
     */
    @Transactional
    public void append(String sessionId, ChatMessageContext ctx) {
        if (ctx == null) return;
        append(sessionId, ctx.getRole(), ctx.getContent(), ctx.getName(), ctx.getPartial());
    }

    /**
     * 如果会话中的消息数量超过阈值，则对旧消息进行摘要并删除原始消息。
     *
     * @param sessionId 会话ID
     */
    @Transactional
    public void summarizeIfNeeded(String sessionId) {
        long count = chatMessageContextDAO.countBySessionId(sessionId);
        if (count <= summaryThreshold) {
            return;
        }
        int toDelete = (int) (count - retainRecent);
        if (toDelete <= 0) return;
        List<ChatMessageContextEntity> oldList = chatMessageContextDAO.findBySessionIdOrderByIdAsc(sessionId, PageRequest.of(0, toDelete));
        if (CollectionUtils.isEmpty(oldList)) return;
        // Build source text then call LLM summarizer
        StringBuilder sb = new StringBuilder();
        for (ChatMessageContextEntity e : oldList) {
            if (sb.length() > summaryMaxChars) break;
            sb.append("[").append(e.getRole()).append("] ").append(e.getContent()).append("\n");
        }
        String source = sb.length() > summaryMaxChars ? sb.substring(0, summaryMaxChars) : sb.toString();
        String summaryText = llmSummarizerService.summarize(source);
        ChatMemorySummaryEntity summary = new ChatMemorySummaryEntity();
        summary.setSessionId(sessionId);
        summary.setSummary(summaryText);
        chatMemorySummaryDAO.save(summary);
        // delete old messages
        chatMessageContextDAO.deleteAll(oldList);
    }

    /**
     * 重命名指定会话的标题。
     *
     * @param sessionId 会话ID
     * @param title 新的标题
     */
    @Transactional
    public void renameSession(String sessionId, String title) {
        chatSessionDAO.findBySessionId(sessionId).ifPresent(s -> {
            s.setTitle(title);
            chatSessionDAO.save(s);
        });
    }

    /**
     * 关闭指定会话（将状态设置为0）。
     *
     * @param sessionId 会话ID
     */
    @Transactional
    public void closeSession(String sessionId) {
        chatSessionDAO.findBySessionId(sessionId).ifPresent(s -> {
            s.setStatus(0);
            chatSessionDAO.save(s);
        });
    }

    /**
     * 列出指定用户的会话，可选按机器人ID过滤，并按最后消息时间降序排列。
     *
     * @param userId 用户ID
     * @param botId 机器人ID，可为null
     * @return 会话列表
     */
    @Transactional(readOnly = true)
    public List<ChatSessionEntity> listSessions(String userId, String botId) {
        // 简单实现：取该用户所有会话（可选按 bot 过滤），按 lastMessageAt desc
        List<ChatSessionEntity> all = chatSessionDAO.findAll();
        return all.stream()
                .filter(s -> userId.equals(s.getUserId()))
                .filter(s -> botId == null || botId.isEmpty() || botId.equals(s.getBotId()))
                .sorted(Comparator.comparing(ChatSessionEntity::getLastMessageAt, Comparator.nullsLast(Date::compareTo)).reversed())
                .collect(Collectors.toList());
    }

    /**
     * 分页列出指定用户的会话，支持按机器人ID过滤和排序。
     *
     * @param userId 用户ID
     * @param botId 机器人ID，可为null
     * @param page 页码（从0开始）
     * @param size 每页大小
     * @param sortField 排序字段
     * @param order 排序方式（asc 或 desc）
     * @return 会话列表
     */
    @Transactional(readOnly = true)
    public List<ChatSessionEntity> listSessions(String userId, String botId, int page, int size, String sortField, String order) {
        String sf = (sortField == null || sortField.isEmpty()) ? "lastMessageAt" : sortField;
        boolean asc = "asc".equalsIgnoreCase(order);
        Sort sort = asc ? Sort.by(sf).ascending()
                : Sort.by(sf).descending();
        Pageable pageable = PageRequest.of(page, size, sort);
        Page<ChatSessionEntity> p;
        if (botId == null || botId.isEmpty()) {
            p = chatSessionDAO.findByUserId(userId, pageable);
        } else {
            p = chatSessionDAO.findByUserIdAndBotId(userId, botId, pageable);
        }
        return p.getContent();
    }

    /**
     * 如果会话标题为空，则设置标题。
     *
     * @param sessionId 会话ID
     * @param title 标题
     */
    @Transactional
    public void setTitleIfEmpty(String sessionId, String title) {
        chatSessionDAO.findBySessionId(sessionId).ifPresent(s -> {
            if (s.getTitle() == null || s.getTitle().isEmpty()) {
                s.setTitle(title);
                chatSessionDAO.save(s);
            }
        });
    }

    /**
     * 对所有会话执行摘要操作（如果需要）。
     */
    @Transactional
    public void summarizeAllSessions() {
        List<ChatSessionEntity> all = chatSessionDAO.findAll();
        for (ChatSessionEntity s : all) {
            try {
                summarizeIfNeeded(s.getSessionId());
            } catch (Exception e) {
                log.warn("summarize session error, sid:{}", s.getSessionId(), e);
            }
        }
    }

    /**
     * 导出指定会话的所有消息。
     *
     * @param sessionId 会话ID
     * @return 消息列表
     */
    @Transactional(readOnly = true)
    public List<ChatMessageContext> exportMessages(String sessionId) {
        List<ChatMessageContextEntity> all = chatMessageContextDAO.findBySessionIdOrderByIdAsc(sessionId, PageRequest.of(0, Integer.MAX_VALUE));
        return all.stream().map(e -> {
            ChatMessageContext c = new ChatMessageContext();
            c.setRole(e.getRole());
            c.setContent(e.getContent());
            c.setName(e.getName());
            c.setPartial(e.getPartial());
            return c;
        }).collect(Collectors.toList());
    }

    /**
     * 删除指定ID的消息。
     *
     * @param id 消息ID
     */
    @Transactional
    public void deleteMessage(Long id) {
        chatMessageContextDAO.deleteById(id);
    }

    /**
     * 删除指定会话的所有消息。
     *
     * @param sessionId 会话ID
     */
    @Transactional
    public void deleteAllMessages(String sessionId) {
        List<ChatMessageContextEntity> all = chatMessageContextDAO.findBySessionIdOrderByIdAsc(sessionId, PageRequest.of(0, Integer.MAX_VALUE));
        chatMessageContextDAO.deleteAll(all);
    }

    /**
     * 将消息上下文序列化为字符串。
     *
     * @param ctx 消息上下文对象
     * @return 序列化后的字符串
     */
    private String serialize(ChatMessageContext ctx) {
        // 以JSON格式存储，字段包含 role/name/content/partial，与JianLiPaiService一致
        return JsonUtil.toJsonString(ctx);
    }

    /**
     * 将字符串反序列化为消息上下文对象。
     *
     * @param s 序列化的字符串
     * @return 消息上下文对象，如果格式不正确则返回null
     */
    private ChatMessageContext deserialize(String s) {
        if (s == null) return null;
        // 优先尝试JSON反序列化
        try {
            ChatMessageContext ctx = JsonUtil.fromJson(s, ChatMessageContext.class);
            if (ctx != null && ctx.getRole() != null) {
                return ctx;
            }
        } catch (Exception ignore) {
        }
        // 兼容历史“role\ncontent”老格式
        int idx = s.indexOf('\n');
        if (idx <= 0) return null;
        String role = s.substring(0, idx);
        String content = s.substring(idx + 1);
        ChatMessageContext ctx = new ChatMessageContext();
        ctx.setRole(role);
        ctx.setContent(content);
        return ctx;
    }

}
